/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jflowmap.views;


import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.WindowListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;

import jflowmap.util.Comment;
import jflowmap.util.PHelp;
import jflowmap.util.piccolo.PBoxLayoutNode;
import jflowmap.util.piccolo.PNodes;
import jflowmap.util.piccolo.PanHandler;
import jflowmap.util.piccolo.PiccoloUtils;
import jflowmap.util.piccolo.ZoomHandler;

import org.apache.log4j.Logger;

import at.fhj.utils.swing.JMsgPane;

import com.itextpdf.text.Chapter;
import com.itextpdf.text.ChapterAutoNumber;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.html.simpleparser.HTMLWorker;
import com.itextpdf.text.pdf.PdfWriter;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.util.PBounds;


/**
 * @author Ilya Boyandin
 */
public class VisualCanvas extends PCanvas  {

	// private static final String ACTION_PNG_EXPORT = "png-export";
	private static final String ACTION_PDF_EXPORT = "pdf-export";

	private static Logger logger = Logger.getLogger(VisualCanvas.class);

	private static final Dimension MIN_SIZE = new Dimension(150, 100);
	private ZoomHandler zoomHandler;
	private boolean autoFitOnBoundsChange = true;
	private PBoxLayoutNode settingButtonsPanel;
	private PBoxLayoutNode modeButtonsPanel;
	private final PHelp helpText;

	private final IView view;
	//  private BlockingGlassPane blockingGlassPane;

	public VisualCanvas(IView view) {
		this.view = view;
		this.helpText = new PHelp();

		setZoomEventHandler(null);
		setZoomHandler(createZoomHandler());
		setPanEventHandler(new PanHandler());
		//  getInputMap().put(KeyStroke.getKeyStroke("alt I"), ACTION_PNG_EXPORT);
		getInputMap().put(KeyStroke.getKeyStroke("alt P"), ACTION_PDF_EXPORT);

		/* getActionMap().put(ACTION_PNG_EXPORT, new AbstractAction() {
      @Override
      public void actionPerformed(ActionEvent e) {
        tryToPaintToPng();
      }
    });*/
		getActionMap().put(ACTION_PDF_EXPORT, new AbstractAction () {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.out.println("PDF export started!");
				tryToPaintToPdf();
			}
		});
		
		settingButtonsPanel = new PBoxLayoutNode(PBoxLayoutNode.Axis.X, 5);
		modeButtonsPanel = new PBoxLayoutNode(PBoxLayoutNode.Axis.X, 5);

		final PCamera ccam = getCamera();
		ccam.addPropertyChangeListener(PCamera.PROPERTY_BOUNDS, new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				updateButtonsPanel();
			}
		});
		ccam.addChild(modeButtonsPanel);
		ccam.addChild(settingButtonsPanel);
		ccam.addChild(helpText);		
	}
	
	public void updateButtonsPanel() {
		final PBounds b = getCamera().getBoundsReference();
		PNodes.setPosition(settingButtonsPanel, b.getMaxX() - settingButtonsPanel.getFullBoundsReference().width - 4, 4);
		PNodes.setPosition(modeButtonsPanel, b.getMaxX() - modeButtonsPanel.getFullBoundsReference().width - 4, settingButtonsPanel.getFullBoundsReference().height + 8);
	}

	public IView getView() {
		return view;
	}

	public PBoxLayoutNode getSettingButtonsPanel() {
		return settingButtonsPanel;
	}

	public PBoxLayoutNode getModeButtonsPanel() {
		return modeButtonsPanel;
	}

	public ZoomHandler getZoomHandler() {
		return zoomHandler;
	}

	public void setZoomHandler(ZoomHandler handler) {
		if (zoomHandler != null) {
			removeInputEventListener(zoomHandler);
		}
		zoomHandler = handler;
		addInputEventListener(zoomHandler);
	}

	protected ZoomHandler createZoomHandler() {
		return new ZoomHandler();
	}

	public boolean getAutoFitOnBoundsChange() {
		return autoFitOnBoundsChange;
	}

	public void setAutoFitOnBoundsChange(boolean autoFitOnBoundsChange) {
		this.autoFitOnBoundsChange = autoFitOnBoundsChange;
	}

	public void setViewZoomPoint(Point2D point) {
		zoomHandler.setFixedViewZoomPoint(point);
	}

	public void setMinZoomScale(double minScale) {
		zoomHandler.setMinScale(minScale);
	}

	public void setMaxZoomScale(double maxScale) {
		zoomHandler.setMaxScale(maxScale);
	}

	public void fitChildrenInCameraView() {
		PCamera camera = getCamera();
		PBounds boundRect = getLayer().getFullBounds();
		camera.globalToLocal(boundRect);
		camera.animateViewToCenterBounds(boundRect, true, 0);
	}

	@Override
	public Dimension getMinimumSize() {
		return MIN_SIZE;
	}

	@Override
	public void setBounds(int x, int y, int w, int h) {
		if (autoFitOnBoundsChange) {
			if (x != getX()  ||  y != getY()  ||  w != getWidth()  ||  h != getHeight()) {
				Rectangle2D oldVisibleBounds =  getVisibleBounds();
				Rectangle2D oldContentBounds = getViewContentBounds();

				super.setBounds(x, y, w, h);

				if (oldVisibleBounds != null) {
					fitVisibleInCameraView(oldVisibleBounds, oldContentBounds);
				}
			}
		} else {
			super.setBounds(x, y, w, h);
		}
	}

	private static final Insets NULL_INSETS = new Insets(0, 0, 0, 0);

	public void fitVisibleInCameraView(Rectangle2D visibleBounds, Rectangle2D contentBounds) {
		if (contentBounds.getWidth() <= 0 || contentBounds.getHeight() <= 0) {
			return;
		}
		final Insets insets;
		//    if (contentBounds.contains(visibleBounds)) { // the whole  is inside the view
			//      insets = getContentInsets();
		//      insets.left += 5;
		//      insets.top += 5;
		//      insets.bottom += 5;
		//      insets.right += 5;
		//    } else {
		insets = NULL_INSETS;
		//    }
		PiccoloUtils.setViewPaddedBounds(getCamera(), visibleBounds, insets);
	}


	/**
	 * Bounds of the part of the visualization (all of the Nodes together) that
	 * is currently visible in the view (in the view coordinate system).
	 *
	 * @return
	 */
	public Rectangle2D getVisibleBounds() {
		//      final PBounds mb = getBounds();
		final PBounds mb = getLayer().getFullBounds();
		if (mb != null) {
			final PBounds vb = getCamera().getViewBounds();
			Rectangle2D.intersect(vb, mb, vb);
			return vb;
		}
		return null;
	}

	/**
	 * Bounds of the area where the visualization should be placed (without the floating
	 * panels) in the camera coordinate system.
	 *
	 * @return
	 */
	private Rectangle2D getContentBounds() {
		//    final Insets insets = getContentInsets();
		//    final PBounds vb = getCamera().getBounds();
		//    vb.x += insets.left;
		//    vb.y += insets.top;
		//    vb.width -= insets.left + insets.right;
		//    vb.height -= insets.top + insets.bottom;
		//    return vb;
		return getBounds();
	}

	private Rectangle2D getViewContentBounds() {
		final Rectangle2D cb = getContentBounds();
		getCamera().localToView(cb);
		return cb;
	}

	/* public void tryToPaintToSvg() {
    try {
      try {
        File f = getOutputFilename(".svg");
        paintToSvg(new OutputStreamWriter(new FileOutputStream(f), "UTF-8"));
        JMsgPane.showInfoDialog(VisualCanvas.this, "View exported to '" + f.getAbsolutePath() + "'");
      } catch (SecurityException se) {
        // try again, but show the output in window
        StringWriter sw = new StringWriter();
        paintToSvg(sw);
        showSvgInApplet(sw.getBuffer().toString());
      }
    } catch (Exception ex) {
      logger.error("Cannot export SVG", ex);
      showProblemDialog(ex);
    }
  }*/

	public void tryToPaintToPdf (File file) {
		try {
			paintToPdf(new FileOutputStream(file));    
		} catch (Exception ex) {
			logger.error("Cannot export PDF", ex);
			showProblemDialog(ex);
		}
	}

	public void tryToPaintToPdf() {
		File f = getOutputFilename(".pdf");
		tryToPaintToPdf(f);
	}

	/*public void tryToPaintToPng() {
	  File f = getOutputFilename(".png");
	  try {
		paintToPng(new FileWriter(f));
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	} catch (Exception e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
  }*/

	public void showProblemDialog(Exception ex) {
		JMsgPane.showProblemDialog(this,
				ex.getClass().getSimpleName() + " " + ex.getMessage() + " " +
						(ex.getCause() != null ? ex.getCause().getMessage() : ""));
	}

	/*public void showSvgInApplet(String svgCode) throws Exception {
    // ShowSourceDialog dialog = new ShowSourceDialog(
    // SwingUtils.getWindowFor(this), "SVG export", svgCode, false);
    // dialog.setVisible(true);

    Applet applet = SwingUtils.getAppletFor(this);
    if (applet != null  &&  applet instanceof JFlowMapApplet) {
      ((JFlowMapApplet)applet).jsFlowMapFunctionCall("showSVGCode",  new Object[] { svgCode });
    }
  }*/

	/*public void paintToPng(final Writer out) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();

    SVGGeneratorContext ctx = SVGGeneratorContext.createDefault(builder.newDocument());
    ctx.setExtensionHandler(new SvgGenExtensionHandler());

    SVGGraphics2D svgGen = new SVGGraphics2D(ctx, false);

    ImageWriterRegistry.getInstance().register(new PNGImageWriter());
    paintComponent(svgGen);

    try {
      svgGen.stream(out, false);
      out.flush();
    } finally {
      if (out != null) out.close();
    }
  }*/

	public void paintToPdf(OutputStream out) throws IOException, DocumentException {

		//Rectangle oldBounds = (Rectangle)this.getBounds().clone();
		/*this.setBounds(this.getX()-(int)(this.getWidth()*0.75),
    			   this.getY()-(int)(this.getWidth()*0.75),
    			   (int) (this.getWidth()*1.5),
    			   (int) (this.getHeight()*1.5));*/
		// this.getView().startExport().toImage(image,  Color.WHITE);


		PCanvas export = getView().startExport();
		BufferedImage image = new BufferedImage((int)(export.getWidth()), (int)(export.getHeight()), BufferedImage.TYPE_INT_RGB);
		Graphics g = image.getGraphics();
		export.paint(g);

		PCanvas legend = getView().exportLegend();
		BufferedImage legendImage = new BufferedImage(legend.getWidth(), legend.getHeight(), BufferedImage.TYPE_INT_RGB);
		g = legendImage.getGraphics();
		legend.paint(g);

		this.getView().endExport();

		Document document = new Document();
		PdfWriter writer = PdfWriter.getInstance(document, out);
		document.open();

		Image img = com.itextpdf.text.Image.getInstance(image, null);
		Image legendImg = com.itextpdf.text.Image.getInstance(legendImage, null);

		double ratio = (document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin()) / image.getWidth();
		img.scalePercent((float) (ratio*100));

		Chapter imgChapter = new ChapterAutoNumber("FoNS: " +  view.getName());
		imgChapter.addSection("Grafik");
		legendImg.scaleToFit(400, 600);

		imgChapter.add(img);
		imgChapter.addSection("Legende");
		if (legendImg.getHeight() > 500*4) {
			legendImg.scaleToFit(200, 800);
			legendImg.setRotationDegrees(90);
		} else {
			legendImg.scaleToFit(200, 500);
		}
		imgChapter.add(legendImg);

		List<Comment> comments = view.getComments();

		Map<String, Set<String>> paragraphs = new HashMap<String, Set<String>>(); 

		for (Comment comment: comments) {
			if (!paragraphs.containsKey(comment.getTitle())) {
				paragraphs.put(comment.getTitle(), new HashSet<String>());
			}
			String text = comment.getText();
			paragraphs.get(comment.getTitle()).add(comment.getText());
		}

		Chapter chapter = new ChapterAutoNumber("Kommentare");
		for (String title: paragraphs.keySet()) {
			chapter.addSection(title);
			for (String s: paragraphs.get(title)) {
				chapter.addAll(HTMLWorker.parseToList(new StringReader(s), null));
			}
		}

		document.add(imgChapter);
		document.add(chapter);

		document.close();   
		writer.close();

	}


	//  public void setBlockInput(boolean block) {
	//    if (blockingGlassPane == null) {
	//      initGlassPane();
	//    }
	//    blockingGlassPane.setVisible(block);
	//  }
	//
	//  private void initGlassPane() {
	//    blockingGlassPane = new BlockingGlassPane();
	//    blockingGlassPane.setVisible(false);
	//
	//    JInternalFrame iframe = SwingUtils.getInternalFrameFor(this);
	//    if (iframe != null) {
	//      iframe.setGlassPane(blockingGlassPane);
	//    } else {
	//      Window w = SwingUtils.getWindowFor(this);
	//      if (w != null  &&  (w instanceof JFrame)) {
	//        ((JFrame)w).setGlassPane(blockingGlassPane);
	//      } else {
	//        JApplet applet = SwingUtils.getAppletFor(this);
	//        applet.setGlassPane(blockingGlassPane);
	//      }
	//    }
	//  }

	public File getOutputFilename(String ext) {


		File file = new File("output" + ext);
		int i = 0;
		while (file.exists()) {
			i++;
			file = new File("output-" + i + ext);
		}
		return file;
	}

	public PHelp getHelpText() {
		return helpText;
	}

	public WindowListener getWindowListener() {
		return this.view.getWindowListener();
	}
}


